use std::path::PathBuf;
use futures::{Future, Stream};
use actix_web::{
    AsyncResponder, App, Error, Json,
    http, HttpRequest, HttpMessage, HttpResponse,
    middleware, FromRequest, pred, multipart, ws
};
use actix_web::middleware::session::{
    SessionStorage, RequestSession, CookieSessionBackend
};
use handler::{
    user_info,
    Verbose,
    Fail,
    WsHandler,
};

use middleware::SongMiddleware;

#[derive(Serialize, Deserialize, Debug)]
struct JsonTest {
    username: String,
    age: u32,
    website: String,
}

pub fn create_application() -> App {
    App::new()
        .middleware(middleware::Logger::default())
        .middleware(SongMiddleware)
        .middleware(middleware::DefaultHeaders::new().header("X-Hello", "hELLo"))
        .middleware(SessionStorage::new(CookieSessionBackend::signed(&[0; 32]).secure(false)))
        .default_resource(|r| {
            // normalize path 实现了 HttpResponse, 它所有东西都找不到的时候, 默认动作是响应 404 错误.
            // r.h(http::NormalizePath::default());

            r.method(http::Method::GET)
                .f(|_req| {
                    println!("not found");
                    HttpResponse::NotFound()
                });

            r.route()
                .filter(pred::Not(pred::Get()))
                .f(|_req| HttpResponse::MethodNotAllowed());
        })
        .resource("/", |r| r.f(index))
        .resource("/verbose", |r| r.h(Verbose::new()))
        .resource("/fail", |r| r.h(Fail::new()))
        .resource("/multipart", |r| r.f(multipart))
        .resource("/response-br-encode", |r| r.f(response_br_encode))
        .resource("/json2", |r| r.method(http::Method::POST).f(json2))
        .resource("/json3", |r| r.method(http::Method::POST).f(json3))
        .resource("/json4", |r| r.method(http::Method::POST).f(json4))
        .resource("/json", |r| r.method(http::Method::POST).with(json))
        .resource("/session", |r| r.method(http::Method::GET).f(session))
        .resource("/{name}/info", |r| {
            r.name("user-info");
            r.method(http::Method::GET).f(user_info::get_user_info);
            r.method(http::Method::POST).f(user_info::set_user_info);
        })
        .route("/{name}/greeting", http::Method::OPTIONS, index2)
        .resource("/{name:[a-z]+}/greeting/{time:\\d+}", |r| r.method(http::Method::GET).f(greeting_times))
        .resource("/path-buf/{test}/{tail:.*}", |r| r.method(http::Method::GET).f(path_buf_test))
        .resource("/url/for/{x}/{y}/z", |r| {
            r.name("ufo");
            r.method(http::Method::GET).f(url_for_handler);
            r.method(http::Method::POST).f(url_for_post_handler);
        })
        .scope("/scope", |scope| {
            scope.resource("/index", |r| {
                r.method(http::Method::GET).f(|_| "Hello scope index".to_owned())
            })
        })
        .external_resource("song-blog", "https://www.songzhigang.com/blog?id={xxx}")
        .handler("/static", actix_web::fs::StaticFiles::new(".").unwrap().show_files_listing())
        .resource("/ws/", |r| r.f(|req| ws::start(req, WsHandler)))
}

fn json(json: Json<JsonTest>) -> String {
    println!("json: \n{:#?}", json);
    format!("Welcome {:?}!", json)
}

fn json2(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
    let x = req.json()
        .from_err()  // convert all errors into `Error`
        .and_then(|val: JsonTest| {
            println!("model: {:?}", val);
            Ok(HttpResponse::Ok().json(val))
        });

    x.responder()
}

// TODO: 这个生成的 future 怎么用?
fn json3(req: &HttpRequest) -> Json<JsonTest> {
    println!("{:?}", req);
    let fjson = Json::<JsonTest>::extract(req);

    fjson.map(|x| {
        println!("{:?}", x);
    }).poll().unwrap();

    Json(JsonTest {
        username: String::from("HelloWorld"),
        age: 12,
        website: "Hi".to_owned(),
    })
}

fn json4(req: &HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
    req.payload()
        .concat2()
        .from_err()
        .and_then(|x| {
            println!("input: {:?}, {:?}", x, serde_json::from_slice::<JsonTest>(&x)?);

            Ok(HttpResponse::Ok().json(JsonTest {
                username: String::from("HelloWorld"),
                age: 12,
                website: "Hi".to_owned(),
            }))
        }).responder()
}

fn index(_req: &HttpRequest) -> & 'static str {
    "Hello World.."
}
fn index2(_req: HttpRequest) -> & 'static str {
    "Hello World.."
}

fn greeting_times(req: &HttpRequest) -> String {
    let name: String = req.match_info().query("name").unwrap_or("unknown".to_owned());
    let mut time: u8 = req.match_info().query("time").unwrap();

    println!("match info: {:?}", req.match_info());
    println!("name: {:?}", name);
    println!("times: {:?}", time);

    let mut result = String::new();
    while time > 0 {
        result.push_str(format!("{}: hello {}\n", time, name).as_str());
        println!("{}", result);
        time -= 1;
    };

    result
}

fn path_buf_test(req: &HttpRequest) -> String {
    let test: String = req.match_info().query("test").unwrap_or("none".to_owned());
    //let tail: String = req.match_info().query("tail").unwrap_or("none".to_owned());
    let pathbuf: PathBuf = req.match_info().query("tail").unwrap_or_else(|e| {
        println!("tail parse error: {:?}", e);
        panic!("exit");
    });

    //format!("test: {:?}, tail: {:?}, pathbuf: {:?}", test, tail, pathbuf)
    format!("test: {:?}, pathbuf: {:?}", test, pathbuf)
}

fn url_for_handler(req: &HttpRequest) -> String {
    let url = req.url_for("user-info", &["hello"]);
    println!("url for result: {:?}", url);


    let url = req.url_for("song-blog", &["hello"]);
    println!("url for result: {:?}", url);

    format!("{}", url.unwrap())
}

fn url_for_post_handler(_req: &HttpRequest) -> String {
    unimplemented!()
}

// TODO: how to make the print statements work?
fn multipart(req: &HttpRequest) -> HttpResponse {
    req.multipart()
        .and_then(|x| {
            match x {
                multipart::MultipartItem::Field(y) => println!("y is: {:?}", y),
                multipart::MultipartItem::Nested(_mp) => {
                    println!("a multipart nested item found.");
                }
            };

            Ok(())
        }).poll();

    HttpResponse::Ok().body("Hello")
}

fn response_br_encode(req: &HttpRequest) -> HttpResponse {
    HttpResponse::Ok()
        .content_encoding(http::ContentEncoding::Auto)
        .body("Hello")
}

fn session(req: &HttpRequest) -> String {
    let haha = req.session().get::<i32>("hahah").unwrap_or(None).unwrap_or(0);

    req.session().set("hahah", haha + 1);
    format!("session: {:?}", haha)
}